﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using NUnit.Framework;
using ObviousCode.TestRunner;

namespace ObviousCode.TestRunner
{
    public class Tester
    {
        static void Main(string[] args)
        {           
            //RunTestsInClass<QueueTests>();            

            Console.ReadLine();
        }

        public static TestResults RunTestsInClass<T>()
        {
            TestResults results = new TestResults();

            MethodInfo setup;
            MethodInfo teardown;
            
            bool found = false;
            bool exceptionExpected = false;

            T instance = Activator.CreateInstance<T>();

            foreach (object attribute in typeof(T).GetCustomAttributes(false))
            {
                if (attribute.GetType() == typeof(TestFixtureAttribute))
                {
                    found = true;
                    break;
                }
            }

            if (!found) throw new InvalidOperationException("Not test class");

            RunAttributedMethod<T, TestFixtureSetUpAttribute>(instance);

            setup = GetAttributedMethod<T, SetUpAttribute>();
            teardown = GetAttributedMethod<T, TearDownAttribute>();

            foreach (MethodInfo method in typeof(T).GetMethods())
            {
                bool isTest = false;
                Type expectedException = null;
                string expectedMessage = string.Empty;
                string methodName;

                foreach (object attribute in method.GetCustomAttributes(false))
                {
                    if (attribute is TestAttribute)
                    {
                        isTest = true;
                        break;
                    }
                }

                if (!isTest) continue;

                methodName = string.Format("{0}.{1}", typeof(T).Name, method.Name);

                if (setup != null)
                {
                    try
                    {
                        setup.Invoke(instance, null);
                    }
                    catch (Exception e)
                    {
                        HandleException(e, results, string.Format("{0}.Setup()", methodName));

                        continue;
                    }
                }

                exceptionExpected = false;
                expectedMessage = string.Empty;

                foreach (object attribute in method.GetCustomAttributes(false))
                {
                    if (attribute is ExpectedExceptionAttribute)
                    {
                        exceptionExpected = true;
                        expectedException = (attribute as ExpectedExceptionAttribute).ExpectedException;
                        expectedMessage = (attribute as ExpectedExceptionAttribute).ExpectedMessage;
                    }
                }                

                try
                {
                    Console.WriteLine("Running: {0}", methodName);

                    method.Invoke(instance, null);

                    if (expectedException == null &&
                        string.IsNullOrEmpty(expectedMessage))
                    {
                        results.Add(TestResult.Pass(methodName));                        
                    }
                    else
                    {
                        results.Add(TestResult.Fail(methodName, "Expected exception not thrown"));
                    }
                }
                catch (Exception e)
                {
                    HandleException(e, results, methodName, exceptionExpected, expectedException, expectedMessage);
                }

                if (teardown != null)
                {
                    try
                    {
                        teardown.Invoke(instance, null);
                    }
                    catch (Exception e)
                    {
                        HandleException(e, results, string.Format("{0}.Teardown()", methodName));

                        continue;
                    }
                }
            }

            RunAttributedMethod<T, TestFixtureTearDownAttribute>(instance);

            return results;
        }

        private static void HandleException(Exception e, TestResults results, string methodName)
        {
            HandleException(e, results, methodName, false, null, null);
        }

        private static void HandleException(Exception e, TestResults results, string methodName, bool exceptionExpected, Type expectedException, string expectedMessage)
        {
            Exception inner = e;

            while (inner.InnerException != null)
            {
                inner = inner.InnerException;
            }

            bool reported = false;

            if (exceptionExpected)
            {
                if (!AssertExceptionTypeCorrect(expectedException, inner.GetType()))
                {
                    results.Add(TestResult.Fail(methodName, inner.Message));

                    reported = true;

                }
                else if (!AssertExpectedMessageCorrect(expectedMessage, inner.Message))
                {
                    results.Add(TestResult.Fail(methodName, 
                        string.Format("Expected Message not correct. Expected {0}, message thrown {1}", 
                        expectedMessage, 
                        inner.Message)));

                    reported = true;
                }
                else
                {
                    results.Add(TestResult.Pass(methodName));
                    reported = true;
                }                
            }

            if (!reported)
            {
                results.Add(TestResult.Fail(methodName, inner.Message));
            }
        }

        private static bool AssertExpectedMessageCorrect(string expectedMessage, string thrownMessage)
        {
            if (string.IsNullOrEmpty(expectedMessage)) return true;

            return expectedMessage.ToLowerInvariant() == thrownMessage.ToLowerInvariant();
        }

        private static bool AssertExceptionTypeCorrect(Type expectedException, Type thrownException)
        {
            if (expectedException == null) return true;

            return expectedException == thrownException;
        }

        private static void RunAttributedMethod<TClass, TAttribute>(TClass instance)
        {
            MethodInfo method = GetAttributedMethod<TClass, TAttribute>();

            if (method == null) return;

            method.Invoke(instance, null);
        }

        private static MethodInfo GetAttributedMethod<TClass, TAttribute>()
        {
            foreach (MethodInfo method in typeof(TClass).GetMethods())
            {
                foreach (object attribute in method.GetCustomAttributes(false))
                {
                    if (attribute.GetType() == typeof(TAttribute))
                    {
                        return method;
                    }
                }
            }

            return null;
        }       

        private static Exception GetInnerExceptionType(Exception e)
        {
            Exception ex = e;

            while (ex.InnerException != null) ex = ex.InnerException;

            return ex;
        }
    }
}
